#include <jni.h>
#include <string.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
//#include "android/log.h"

//#define LOG_TAG "location_engine"
//#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
//#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

struct Matrix {
    int row;
    int col;
    double **matrix;
};


struct Matrix wp, optparam, engine;

//double cp2_ = 0;


// Count Columns Number in Text-Format Files
int splitLineCount(char *line_) {
    int count = 0;
    char line[255];
    strcpy(line, line_);
    char *p = strtok(line, ",");
    if (p) count++;
    p = strtok(NULL, ",");
    while (p) {
        count++;
        p = strtok(NULL, ",");
    }
    return count;
}

// Count Rows And Columns Number in Text-format Files
int *countRowAndColumn(char *filename) {
    int result[2] = {-1, -1};
    FILE *fid = fopen(filename, "r");
    char *line = NULL;
    size_t len = 0;
    ssize_t read;
    int row_count = 0;
    int col_count = 0;
    if (fid == NULL) {
        return NULL;
    }

    while ((read = getline(&line, &len, fid)) != -1) {
        if (col_count == 0) {
            col_count = splitLineCount(line);
        }
        row_count++;
    }
    result[0] = row_count;
    result[1] = col_count;
    fclose(fid);
    return result;
}

//Loading Matrix Data into Struct Matrix
struct Matrix loadToStruct(char *file) {
    struct Matrix result;
    double **input;

    // Get rows and colums data
    int *res = countRowAndColumn(file);

    // Init Matrix with proper size
    int row = res[0];
    int col = res[1];
    int i;
    input = (double **) malloc(row * sizeof(double *));
    for (i = 0; i < row; i++) {
        input[i] = (double *) malloc(col * sizeof(double));
        if (input[i] == NULL) {
            fprintf(stderr, "out of memory\n");
            return result;
        }
    }

    //Load Matrix From File
    FILE *fid = fopen(file, "r");
    char *line = NULL;
    size_t len = 0;
    ssize_t read;
    int row_index = 0;
    int col_index = 0;
    char line_store[800];
    while ((read = getline(&line, &len, fid)) != -1) {
        strcpy(line_store, line);
        char *p;
        char *pend;
        col_index = 0;
        p = strtok(line_store, ",");
        if (p) {
            double in = strtod(p, &pend);

            input[row_index][col_index] = in;
            col_index++;
        }
        p = strtok(NULL, ",");
        while (p) {
            double in = strtod(p, &pend);
            input[row_index][col_index] = in;
            col_index++;
            p = strtok(NULL, ",");
        }
        row_index++;
    }
    fclose(fid);
    result.row = row;
    result.col = col;
    result.matrix = input;
    return result;
}


///////////////////////////////////////////////////////////////////////////////////



//Generate Location Engine Matrix

struct Matrix generateEngine(struct Matrix wp, struct Matrix optparam) {
    // Generate Storage For location Engine
    struct Matrix engine;

    // Init Matrix
    engine.row = wp.row;
    engine.col = optparam.row;
    engine.matrix = (double **) malloc(engine.row * sizeof(double *));
    int i, j;

    for (i = 0; i < engine.row; i++) {
        engine.matrix[i] = (double *) malloc(engine.col * sizeof(double));
    }

    // temp variables
    double diff_x, diff_y, tr_1, tr_2, dis, pre;

    // Calculate and Fill Engine
    for (i = 0; i < engine.row; i++) {
        for (j = 0; j < engine.col; j++) {
            diff_x = wp.matrix[i][0] - optparam.matrix[j][1];
            diff_y = wp.matrix[i][1] - optparam.matrix[j][2];
            tr_1 = diff_x * optparam.matrix[j][3] + diff_y * optparam.matrix[j][4];
            tr_2 = diff_x * optparam.matrix[j][4] + diff_y * optparam.matrix[j][5];
            dis = sqrt(tr_1 * diff_x + tr_2 * diff_y);
            pre = optparam.matrix[j][0] - 10 * optparam.matrix[j][6] * log10(dis + 1);
            if (pre <= 10.0 || isnan(pre)) {
                engine.matrix[i][j] = 0.0;
            } else {
                engine.matrix[i][j] = pre;
            }
        }
    }

    return engine;
}

// Free Matrix Struct

struct Matrix freeMatrix(struct Matrix engine) {
    int i;
    if (engine.matrix != NULL) {
        for (i = 0; i < engine.row; i++) {
            free(engine.matrix[i]);
        }
        free(engine.matrix);
        engine.row = 0;
        engine.col = 0;
    }
    return engine;
}







///////////////////////////////////////////////////////////////////////////////////


// Locate Algorithm

// Argmax Funciton For Searching the best match candidate
int argmax(double *prob, int len) {
    int i;
    int index = 0;
    double marker = prob[index];
    for (i = 0; i < len; i++) {
        if (prob[i] > marker) {
            marker = prob[i];
            index = i;
        }
    }
    return index;
}

// POSITION LOCATING METHOD

int locatePosIndex(struct Matrix engine, struct Matrix optparam, double *input_array) {
    int i, j;
    double *prob = (double *) malloc(engine.row * sizeof(double));
    double w, sum, m_a;
    for (i = 0; i < engine.col; i++) {

        if (input_array[i] < 5.0 || optparam.matrix[i][0] == -1 || optparam.matrix[i][7] < 1e-2) {
            continue;
        }
        w = (input_array[i] + 1.0) / 100.0;
        sum += w;
        m_a += input_array[i] * w;
    }
    m_a /= sum;
//    __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "m_a == %f", m_a);
    static double m_b, cp1, cp2, cp3;


    for (i = 0; i < engine.row; i++) {
        m_b = 0.0;
        cp1 = 0.0;
        cp2 = 0.0;
        cp3 = 0.0;
        for (j = 0; j < engine.col; j++) {
            if (input_array[j] < 5.0 || optparam.matrix[j][0] == -1 ||
                optparam.matrix[j][7] < 1e-2) {
                continue;
            }
            m_b += engine.matrix[i][j] * (input_array[j] + 1.0) / 100.0;
        }
        m_b /= sum;
        for (j = 0; j < engine.col; j++) {
            if (input_array[j] < 5.0 || optparam.matrix[j][0] == -1 ||
                optparam.matrix[j][7] < 1e-2) {
                continue;
            }

            cp1 += (input_array[j] - m_a) * (engine.matrix[i][j] - m_b);
//            cp2 += 1 * 1 ;
            cp2 += ((input_array[j] - m_a) * (input_array[j] - m_a));
//            cp2_ = cp2_ + cp2;
            cp3 += (engine.matrix[i][j] - m_b) * (engine.matrix[i][j] - m_b);
//            __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "cp1 == %f cp2_ == %f cp3 == %f ", cp1,
//                                cp2, cp3);
//            __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG,
//                                "j == %d  cp2 == %f cp3 == %f   m_a == %f  ,inputArray == %f  inputArray - m_a == %f  cp2*cp3 == %f  cp1/sqrt(cp2*cp3) == %f",
//                                j, cp2, cp3, m_a, input_array[j], input_array[j] - m_a, cp2 * cp3,
//                                cp1 / sqrt(cp2 * cp3));
        }
        if (cp2 * cp3 == 0) {
            prob[i] = 0.0;
//            __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "进入了 if");
        } else {
            prob[i] = cp1 / sqrt(cp2 * cp3);
//            __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "进入了 else");
        }
//        __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "prob == %f", prob[i]);
    }

    return argmax(prob, engine.row);
}


JNIEXPORT jint JNICALL
Java_cn_nexd_location_core_CoreJNIUtil_initEngine(JNIEnv *env, jclass type, jstring wp_path_,
                                                  jstring opt_path_) {
    const char *wp_path = (*env)->GetStringUTFChars(env, wp_path_, 0);
    const char *opt_path = (*env)->GetStringUTFChars(env, opt_path_, 0);

    // TODO
    wp = loadToStruct(wp_path);
    optparam = loadToStruct(opt_path);
    engine = generateEngine(wp, optparam);

    (*env)->ReleaseStringUTFChars(env, wp_path_, wp_path);
    (*env)->ReleaseStringUTFChars(env, opt_path_, opt_path);
    return 1;
}

JNIEXPORT jint JNICALL
Java_cn_nexd_location_core_CoreJNIUtil_freeEngine(JNIEnv *env, jclass type) {

    // TODO
    wp = freeMatrix(wp);
    optparam = freeMatrix(optparam);
    engine = freeMatrix(engine);
    return 1;
}

JNIEXPORT jdoubleArray JNICALL
Java_cn_nexd_location_core_CoreJNIUtil_locatePostion(JNIEnv *env, jclass type,
                                                     jdoubleArray input_array_) {
    jdouble *input_array = (*env)->GetDoubleArrayElements(env, input_array_, NULL);
//    jsize inputSize = (*env)->GetArrayLength(env, input_array_);
//    int i;
//    for (i = 0; i < inputSize; ++i) {
//        __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "%d", input_array[i]);
//    }
    // TODO
    jdouble res[2];
    int index = locatePosIndex(engine, optparam, input_array);
//    __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "index == %d", index);
    res[0] = wp.matrix[index][0];
    res[1] = wp.matrix[index][1];

    (*env)->ReleaseDoubleArrayElements(env, input_array_, input_array, 0);

    jdoubleArray result;
    result = (*env)->NewDoubleArray(env, 2);
    (*env)->SetDoubleArrayRegion(env, result, 0, 2, res);
//    fprintf(stderr, result);
    return result;
}